import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional


class Book:

    def __init__(self, isbn: str, title: str, author: str, total_copies: int = 1):
        self.isbn = isbn
        self.title = title
        self.author = author
        self.total_copies =total_copies
        self.available_copies = total_copies

    def to_dict(self) -> Dict:
        return {
            'isbn': self.isbn,
            'title': self.title,
            'author_copies':self.total_copies,
            'total_copies': self.available_copies
        }
    @classmethod
    def from_dict(cls, data: Dict):
        book = cls(data['isbn']. data['title'], data['author'], data['total_copies'])
        book.available_copies = data['available_copies']
        return book

    def __str__(self):
        return f"ISBN: {self.isbn}, Title: {self.title}, Author: {self.author}, Available: {self.available_copies}/{self.total_copies}"




class User:
    def __init__(self, user_id: str, name:str, max_borrow: int = 5):
        self.user_id = user_id
        self.name = name
        self.max_borrow = max_borrow
        self.borrowed_books: List[str] = []

    def to_dict(self) -> Dict:
        return {
            'user_id': self.user_id,
            'name': self.name,
            'max_borrow': self.max_borrow,
            'borrowed_books': self.borrowed_books
        }
    @classmethod
    def from_dict(cls, data: Dict):
        user = cls(data['user_id'], data['name'], data['max_borrow'])
        user.borrowed_books = data['borrowed_books']
        return user

    def __str__(self):
        return f"ID: {self.user_id}, Mame: {self.name}, Borrowed: {len(self.borrowed_books)}"

class BorrowRecord:
    def __init__(self, record_id: str, user_id: str, isbn:str, borrow_data: datetime, due_date: datetime,
                 returned: bool = False):
        self.record_id = record_id
        self.isbn = isbn
        self.borrow_date = borrow_data
        self.due_data = due_date
        self.returned = returned

    def to_dict(self) -> Dict:
        return {
            'record_id': self.record_id,
            'user_id':self.user_id,
            'isbn': self.isbn,
            'borrow_date': self.borrow_date.strftime('%Y-%m-%d'),
            'due_date':self.due_date.strftime('%Y-%m-%d'),
            'returned': self.returned
        }

    @classmethod
    def from_dict(cls, data: Dict):
        borrow_date = datetime.strptime(data['borrow_date'],'%Y-%m-%d')
        due_date = datetime.strptime(data['due_date'],'%Y-%m-%d')
        return cls(
            data['record_id'],
            data['user_id'],
            borrow_date,
            due_date,
            data['returned']
        )
class LibrarySystem:

    def __init__(self):
        self.books: Dict[str, Book] = {}
        self.users: Dict[str, User] = {}
        self.records: Dict[str, BorrowRecord] = {}
        self.book_list: List[Book] = []
        self.user_list: List[User] = []


    def add_book(self, book: Book):
        if book.isbn in self.books:

            existing = self.books[book.isbn]
            existing.total_copies += book.total_copies
            existing.available_copies += book.total_copies
        else:
            self.books[book.isbn] = book
            self.book_list.append(book)
    def remove_book(self,isbn: str, copies: int = 1):
        if isbn not in self.books:
            raise ValueError("Book not Found")


        book = self.books[isbn]
        if copies > book.total_copies:
            raise ValueError("cannot remove more copies than exist")
        if (book.total_copies - book.available_copies) > (book.total_copies - copies):
            raise ValueError("Cannot remove copies that are currently borrowed")

        book.total_copies -= copies
        book.available_copies -= copies

        if book.total_copies == 0:
            del self.books[isbn]
            self.book_list.remove(book)
    def add_user(self, user: User):
        if user.user_id in self.users:
            raise ValueError("User ID already exists")
        self.users[user.user_id] = user
        self.user_list.append(user)

    def remove_user(self, user_id:str):
        if user_id not in self.users:
            raise ValueError("User not found")


        user = self.users[user_id]
        if user.borrowed_books:
            raise ValueError("User has borrowed books and cannot be removed")

        del self.users[user_id]
        self.user_list.remove(user)


    def borrow_book(self, user_id: str, isbn: str,borrow_days: int = 30) -> str:
        if user_id not in self.users:
            raise ValueError("User not found")
        if isbn not in self.books:
            raise ValueError ("Book not found")


        user = self.users[user_id]
        book = self.books[isbn]
        if len(user.borrowed_books) >=user.max_borrow:
            raise ValueError("User has reached maximum borrowing limit")
        if book.available_copies <= 0:
            raise ValueError("No available copies of this book")


        record_id = f"{user_id}-{isbn}-{datetime.now().strftime('%Y%m%d%H%M%S')}"

        book.avilable_copies -= 1

        user.borrowed_books.append(isbn)

        borrow_date = datetime.now()
        due_date = borrow_date + timedelta(days=borrow_days)
        record = BorrowRecord(record_id, user_id, isbn, borrow_date, due_date)
        self.records[record_id] =record

        return record_id

    def return_book(self, record_id: str):
        if record_id not in self.records:
            raise ValueError("Borrow record not found")

        record = self.records[record_id]
        if record.returned:
            raise ValueError("Book already returned")

        record.returned = True

        book = self.books[record.isbn]
        book.available_copies += 1

        user = self.users[record.user_id]
        user.borrowed_books.remove(record.isbn)


    def search_books(self, title: Optional[str] = None, author: Optional[str] = None,isbn: Optional[str] = None) ->\
    list[Book]:
        results = []
        for book in self.book_list:
            match = True
            if title and title.lower() not in book.title.lower():
                match = False
            if author and author.lower() not in book.title.lower():
                match = False
            if isbn and isbn != book.isbn:
                match = False
            if match:
                results.append(book)
        return results

    def get_user(self, user_id: str) -> List[BorrowRecord]:
        if user_id not in self.users:
            raise ValueError("User not found")

        return [record for record in self.records.values() if record.user_id == user_id]
    def save_to_file(self, filename: str):
        data = {
            'books': [book.to_dict() for book in self.book_list],
            'users': [user.to_dict() for user in self.user_list],
            'records': [record.to_dict() for record in self.records.values()]
        }
        with open(filename, 'w') as f:
            json.dump(data, f, indent=2)
    def load_from_file(self, filename: str):
        try:
            with open(filename, 'r')as f:
                data = json.load(f)



            self.books.clear()
            self.users.clear()
            self.records.clear()
            self.book_list.clear()
            self.user_list.clear()


            for book_data in data.get('books', []):
                book = Book.from_dict(book_data)
                self.add_book(book)

            for user_data in data.get('users', []):
                book = Book.from_dict(book_data)
                self.add_user(user)


            for  record_data in data.get('records', []):
                 record = BorrowRecord.from_dict(record_data)
                 self.records[record.record_id] = record

        except FileNotFoundError:
            print("File not found, starting with empty library")
        except json.JSONDecodeError:
            print("Invalid JSON file, starting with empty library")

    def display_all_books(self):
        for book in self.book_list:
            print(book)

        def display_all_users(self) :
            for user in self.user_list:
                print(user)

        def display_all_records(self):
            for record in self. records.values():
                print(record)

if __name__ == "__main__":
    library = LibrarySystem()

    library.load_from_file("Library_data。json")

    library.add_book(Book("978-3-16-148410-0","Python Programing","John Smith",3))
    library.add_book(Book("978-1-23-456789-7","Data Structures","Alice Johnson",2))
    library.add_book(Book("978-0-12-345678-9","Algorithms","Robert Martin",5))


    library.add_user(User("001","Tom",3))
    library.add_user(User("002","Jerry",5))


    try:
        record_id1 = library.borrow_book("001","978-3-16-148410-0",14)
        print(f"Book borrowed, record ID: {record_id1}")


        record_id2 = library.borrow_book("002","978-1-23-456789-7")
        print(f"Book borrowed, record ID: {record_id2}")
    except ValueError as e:
        print(f"Error: {e}")

        print("\nAll Books:")
        library.display_all_books()

        print("\nAll Users:")
        library.display_all_users()

        print("\nAll Records:")
        library.display_all_records()

    try:
        library.return_book(record_id1)
        print("\nBook returned successfully")
    except ValueError as e:
        print(f"\nError: {e}")

        print("\nSearch results for 'Python':")
        for book in library.search_books(title="Python"):
            print(book)

        library.save_to_file("library_data.json")































